/*******************************************************************************
 * Copyright (c) 2009 Paul VanderLei, Simon Archer, Jeff McAffer and others. All
 * rights reserved. This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License v1.0 which
 * accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Toast is an Equinox/OSGi system developed for the book Eclipse, Equinox and
 * OSGi - Creating Highly Modular Java Applications See http://equinoxosgi.org
 *
 * Contributors: Paul VanderLei, Simon Archer and Jeff McAffer - initial API and
 * implementation
 *******************************************************************************/
package org.equinoxosgi.toast.internal.core;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.service.log.LogService;

class ConsoleLog implements LogService {
	private static final int DEFAULT_BUFFER_SIZE = 1024; // 1K
	private static final String LINE_SEPARATOR = System.getProperty("line.separator");
	private static final String LOG_LEVEL_DEBUG_VALUE = "DEBUG";
	private static final String LOG_LEVEL_ERROR_VALUE = "ERROR";
	private static final String LOG_LEVEL_INFO_VALUE = "INFO";

	private static final String LOG_LEVEL_UNKNOWN_VALUE = "<unknown>";
	private static final String LOG_LEVEL_WARNING_VALUE = "WARNING";

	private StringBuffer buffer;

	ConsoleLog() {
		super();
		setBuffer(new StringBuffer(ConsoleLog.DEFAULT_BUFFER_SIZE));
	}

	private String formatLogMessage(final ServiceReference reference, final int level, final String message) {
		final StringBuffer buffer = getBuffer();
		final int count = buffer.length();
		buffer.delete(0, count); // Reset the buffer.
		final String prefix = getPrefix(level);
		buffer.append(prefix);
		buffer.append(' ');
		buffer.append(message);
		printServiceReferenceOn(buffer, reference);
		buffer.append(ConsoleLog.LINE_SEPARATOR);
		final String logMessage = buffer.toString();
		return logMessage;
	}

	private StringBuffer getBuffer() {
		return buffer;
	}

	private String getLevelText(final int level) {
		String text;

		switch (level) {
		case LogService.LOG_ERROR:
			text = ConsoleLog.LOG_LEVEL_ERROR_VALUE;
			break;
		case LogService.LOG_WARNING:
			text = ConsoleLog.LOG_LEVEL_WARNING_VALUE;
			break;
		case LogService.LOG_INFO:
			text = ConsoleLog.LOG_LEVEL_INFO_VALUE;
			break;
		case LogService.LOG_DEBUG:
			text = ConsoleLog.LOG_LEVEL_DEBUG_VALUE;
			break;
		default:
			text = ConsoleLog.LOG_LEVEL_UNKNOWN_VALUE;
			break;
		}

		return text;
	}

	private OutputStream getOutputStream(final int level) {
		OutputStream stream;

		if ((level == LogService.LOG_INFO) || (level == LogService.LOG_DEBUG)) {
			stream = System.out;
		} else {
			stream = System.err;
		}

		return stream;
	}

	private String getPrefix(final int level) {
		final String levelText = getLevelText(level);

		final StringBuffer buffer = new StringBuffer(10);
		buffer.append('[');
		buffer.append(levelText);
		buffer.append(']');

		final String prefix = buffer.toString();
		return prefix;
	}

	private List getServiceNames(final ServiceReference reference) {
		final String[] names = (String[]) reference.getProperty(Constants.OBJECTCLASS);
		final List list = new ArrayList(names.length);

		for (final String name : names) {
			list.add(name);
		}

		return list;
	}

	/**
	 * @see org.osgi.service.log.LogService#log(int, java.lang.String)
	 */
	@Override
	public void log(final int level, final String message) {
		log(null, level, message, null);
	}

	/**
	 * @see org.osgi.service.log.LogService#log(int, java.lang.String,
	 *      java.lang.Throwable)
	 */
	@Override
	public void log(final int level, final String message, final Throwable throwable) {
		log(null, level, message, throwable);
	}

	/**
	 * @see org.osgi.service.log.LogService#log(org.osgi.framework.ServiceReference,
	 *      int, java.lang.String)
	 */
	@Override
	public void log(final ServiceReference reference, final int level, final String message) {
		log(reference, level, message, null);
	}

	/**
	 * @see org.osgi.service.log.LogService#log(org.osgi.framework.ServiceReference,
	 *      int, java.lang.String, java.lang.Throwable)
	 */
	@Override
	public void log(final ServiceReference reference, final int level, final String message,
			final Throwable throwable) {
		final OutputStream stream = getOutputStream(level);

		synchronized (this) {
			final String logMessage = formatLogMessage(reference, level, message);
			final byte[] bytes = logMessage.getBytes();

			try {
				stream.write(bytes);
				stream.flush();

				if (throwable != null) {
					final PrintStream printStream = new PrintStream(stream);
					throwable.printStackTrace(printStream);
					printStream.flush();
				}
			} catch (final IOException exception) {
				exception.printStackTrace();
			}
		}
	}

	private void printServiceReferenceOn(final StringBuffer buffer, final ServiceReference reference) {
		if (reference == null) {
			return; // Early return.
		}
		final Bundle bundle = reference.getBundle();

		if (bundle != null) {
			final String symbolicName = bundle.getSymbolicName();
			final long id = bundle.getBundleId();
			buffer.append("bundle");
			buffer.append('=');
			buffer.append(symbolicName);
			buffer.append(' ');
			buffer.append('[');
			buffer.append(id);
			buffer.append(']');
			buffer.append(',');
			buffer.append(' ');
		}

		buffer.append("services");
		buffer.append('=');
		buffer.append('[');

		final List names = getServiceNames(reference);
		final Iterator iterator = names.iterator();

		while (iterator.hasNext() == true) {
			final Object name = iterator.next();
			buffer.append(name);

			if (iterator.hasNext() == true) {
				buffer.append(',');
				buffer.append(' ');
			}
		}

		buffer.append(']');
	}

	private void setBuffer(final StringBuffer buffer) {
		this.buffer = buffer;
	}
}